/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.bofaconnectors.provision;

import Thor.API.Operations.tcFormDefinitionOperationsIntf;
import Thor.API.Operations.tcFormInstanceOperationsIntf;
import Thor.API.Operations.tcObjectOperationsIntf;
import Thor.API.Operations.tcProvisioningOperationsIntf;
import com.thortech.util.logging.Logger;
import com.thortech.xl.dataaccess.tcDataProvider;
import com.thortech.xl.vo.ResourceData;
import java.util.*;
import org.oimwrapper.connectors.OIMAdapterBase;
import org.oimwrapper.connectors.internal.OIMObjectFormUtilities;
import org.oimwrapper.connectors.internal.OIMUtilityGetter;

/**
 *
 */
public class ProvisionCustomRequest extends OIMAdapterBase implements ProvisioningInterface {
    
    private static final String childRoleTableName = "Lookup.ChildRoleTableName";
    private static final Logger mylogger = Logger.getLogger(ProvisionCustomRequest.class.getName());
    
    private tcProvisioningOperationsIntf provOps = null;
    private tcFormInstanceOperationsIntf formInstOps = null;
    private tcFormDefinitionOperationsIntf formDefOps = null;
    private tcObjectOperationsIntf objectOp = null;
    private OIMObjectFormUtilities objFormUtils = null;

    public ProvisionCustomRequest(String loggerName,tcDataProvider database)
    {
        super(loggerName,database);
    }
    
    public ProvisionCustomRequest(tcDataProvider database)
    {
        super("WRAPPER.LOGGER",database);
    }

    public void dummyMethod()
    {

    }
    
    public String init()
    {
        
        try
        {
            provOps = (tcProvisioningOperationsIntf)getUtility(Thor.API.Operations.tcProvisioningOperationsIntf.class);
            formInstOps = (tcFormInstanceOperationsIntf)getUtility(Thor.API.Operations.tcFormInstanceOperationsIntf.class);
            formDefOps = (tcFormDefinitionOperationsIntf)getUtility(Thor.API.Operations.tcFormDefinitionOperationsIntf.class);
            objectOp = (tcObjectOperationsIntf)getUtility(Thor.API.Operations.tcObjectOperationsIntf.class);
            objFormUtils = OIMUtilityGetter.getObjectFormUtilities(logger, objectOp,formInstOps, formDefOps);
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE;
        }
        return SUCCESS;
    }
    
    public String copyRequestChildRecords(long instanceKey)
    {
        try
        {
            long obi = this.getObjectInstanceKeyForOrg(instanceKey, "Requests");
            Map[] provisionParentAttrs = objFormUtils.getObjectFormChildValues(obi, "UD_GRES_OA",false,null);
            Map[] provisionChildAttrs = objFormUtils.getObjectFormChildValues(obi, "UD_GRES_OC", false, null);
            Map tmpMap = new HashMap();
            
            for(Map m : provisionParentAttrs)
            {
                tmpMap.put("UD_GRES_PA_NAME",m.get("UD_GRES_OA_NAME"));
                tmpMap.put("UD_GRES_PA_VALUE",m.get("UD_GRES_OA_VALUE"));
                mylogger.debug("Copy Parent Data:" + tmpMap);
                this.addProcessFormChildValue(instanceKey, "UD_GRES_PA", tmpMap);
                tmpMap.clear();
            }
            for(Map m : provisionChildAttrs)
            {
                tmpMap.put("UD_GRES_PC_NAME",m.get("UD_GRES_OC_NAME"));
                tmpMap.put("UD_GRES_PC_VALUE",m.get("UD_GRES_OC_VALUE"));
                tmpMap.put("UD_GRES_PC_SEQNUM",m.get("UD_GRES_OC_SEQNUM"));
                mylogger.debug("Copy Child Data:" + tmpMap);
                this.addProcessFormChildValue(instanceKey, "UD_GRES_PC", tmpMap);
                tmpMap.clear();
            }
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE + ":" + e.toString();
        }
        return SUCCESS;
    }
    
    public String checkRequestType(long instanceKey) throws Exception
    {
        try
        {
            Map formData = getProcessFormValues(instanceKey);
            mylogger.debug("Provision Request Process Form:" + formData);
            String requestType = (String)formData.get("UD_GRES_P_REQUESTTYPE");
            logger.debug("Checking Request Type:" + requestType);
            if (requestType == null || requestType.trim().length() == 0)
            {
                return FAILURE + ":Invalid Request Type:" + requestType;
            }
            return requestType.toUpperCase();
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            throw new Exception("APIError",e);
        }
    }
    
    public String createUser(long instanceKey)
    {
        Map formData = null;
        try
        {
            formData = getProcessFormValues(instanceKey);
            mylogger.info("Provision Request Process Form:" + formData);
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE + ":" + e.toString();
        }
           
        String resName = (String)formData.get("UD_GRES_P_RESOURCENAME");
        String requestor = (String)formData.get("UD_GRES_P_REQUESTER");
        String requestType = (String)formData.get("UD_GRES_P_REQUESTTYPE");
        String targetUser = (String)formData.get("UD_GRES_P_TARGETUSERID");
        String externalReqid = (String)formData.get("UD_GRES_P_EXTERNALREQID");
            
        try
        {
            // is valid resource object?
            long objKey = this.getResourceObject(resName);
            if (objKey == 0L)
            {
                mylogger.error("Resource Not Found:" + resName);
                return FAILURE + "Resource Not Found:" + resName;
            }
                
            // does user have resource?
            boolean activeOnly = false;
            Map[] userObjects = this.getObjectsForUser(resName,targetUser,activeOnly);
            long targetUserKey = this.getOimUser(targetUser);
            
            if (userObjects.length == 0)
            {
                mylogger.debug("Provisioning Resource " + resName + " To " + targetUser);
                return provisionResource(targetUser,resName,instanceKey);
            }
            mylogger.info("User Has Resource Provisioned");
            
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE + ":" + e.toString();
        }
        return SUCCESS;
        
    }
    
    public String addUserToGroup(long instanceKey)
    {
        Map formData = null;
        try
        {
            formData = getProcessFormValues(instanceKey);
            mylogger.info("addUserToGroup Request Process Form:" + formData);
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE + ":" + e.toString();
        }
           
        Map[] provisionChildAttrs = null;
        
        String childTableName = (String)formData.get("UD_GRES_P_ROLE_TABLENAME");
        String resName = (String)formData.get("UD_GRES_P_RESOURCENAME");
        String targetUser = (String)formData.get("UD_GRES_P_TARGETUSERID");
        boolean activeOnly = true;
        Map[] userObjects = null;
        long resourceInstanceKey = 0L;
        
        try
        {
            userObjects = this.getObjectsForUser(resName,targetUser,activeOnly);
            if (userObjects.length == 0)
            {
                mylogger.error("No Resource Found");
                return SUCCESS;
            }
            if (userObjects.length > 1)
            {
                mylogger.error("Too Many Resource Found");
                return SUCCESS;
            }
            mylogger.debug("Got Resource For Group:" + userObjects[0]);
            String tmp = (String)userObjects[0].get("Process Instance.Key");
            resourceInstanceKey = Long.parseLong(tmp);
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE;
        }
        
        try
        {
            provisionChildAttrs = getProcessFormChildValues(instanceKey, "UD_GRES_PC", false, null);
            return provisionGroup(childTableName,provisionChildAttrs,resourceInstanceKey);
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE + ":" + e.toString();
        }
        
    }
    
    public String removeUserFromGroup(long instanceKey)
    {
        Map formData = null;
        try
        {
            formData = getProcessFormValues(instanceKey);
            mylogger.info("removeUserFromGroup Request Process Form:" + formData);
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE + ":" + e.toString();
        }
           
        Map[] provisionChildAttrs = null;
        
        String childTableName = (String)formData.get("UD_GRES_P_ROLE_TABLENAME");
        String resName = (String)formData.get("UD_GRES_P_RESOURCENAME");
        String targetUser = (String)formData.get("UD_GRES_P_TARGETUSERID");
        boolean activeOnly = true;
        Map[] userObjects = null;
        long resourceInstanceKey = 0L;
        
        try
        {
            userObjects = this.getObjectsForUser(resName,targetUser,activeOnly);
            if (userObjects.length == 0)
            {
                mylogger.error("No Resource Found");
                return SUCCESS;
            }
            if (userObjects.length > 1)
            {
                mylogger.error("Too Many Resource Found");
                return SUCCESS;
            }
            mylogger.debug("Got Resource For Group:" + userObjects[0]);
            String tmp = (String)userObjects[0].get("Process Instance.Key");
            resourceInstanceKey = Long.parseLong(tmp);
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE;
        }
        
        try
        {
            provisionChildAttrs = getProcessFormChildValues(instanceKey, "UD_GRES_PC", false, null);
            return deProvisionGroup(childTableName,provisionChildAttrs,resourceInstanceKey);
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE + ":" + e.toString();
        }
        
    }
    
    private String deProvisionGroup(String childTableName,Map[] provisionChildAttrs,long instanceKey) throws Exception
    {
        try
        {
            mylogger.debug("DE provisionGroup Resource Child Entries to:" + childTableName);
            mylogger.debug("Instance:" + instanceKey);
            List<Map> childRecords = buildTargetResourceChildRecords(provisionChildAttrs);
            Map[] currentChildren = this.getProcessFormChildValues(instanceKey, childTableName);
            if (currentChildren == null || currentChildren.length == 0)
                return SUCCESS;
            
            for(Map m : childRecords)
            {
                //mylogger.debug("ChildMap:" + m);
                Map matched = findMatchingChildRecord(currentChildren,m);
                if (matched == null)
                    continue;
                mylogger.debug("Removing:" + matched);
                this.removeProcessDataChildValue(instanceKey, childTableName, matched);
                //this.addProcessFormChildValue(instanceKey, childTableName, m);
            }
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE + ":" + e.toString();
        }
        return SUCCESS;
    }
    
    private String provisionGroup(String childTableName,Map[] provisionChildAttrs,long instanceKey) throws Exception
    {
        try
        {
            mylogger.debug("provisionGroup Resource Child Entries to:" + childTableName);
            mylogger.debug("Instance:" + instanceKey);
            List<Map> childRecords = buildTargetResourceChildRecords(provisionChildAttrs);
            for(Map m : childRecords)
            {
                mylogger.debug("ChildMap:" + m);
                this.addProcessFormChildValue(instanceKey, childTableName, m);
            }
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE + ":" + e.toString();
        }
        return SUCCESS;
    }
    
    private String provisionResource(String targetUser,String resName, long instanceKey)
    {
        Map provisionParentData = null;
        Map[] provisionParentAttrs = null;
        Map[] provisionChildAttrs = null;
        
        String childTableName = null;
        
        try
        {
            provisionParentData = this.getProcessFormValues(instanceKey);
            long obi = this.getObjectInstanceKeyForOrg(instanceKey, "Requests");
            
            // create default adapter to copy the object form data to the process form
            // prior to kicking off the provisioning tasks.
            provisionParentAttrs = getProcessFormChildValues(instanceKey, "UD_GRES_PA",false,null);
            
            mylogger.debug("Show Parent Attributes");
            for(int a=0;a<provisionParentAttrs.length;a++)
            {
                mylogger.debug("Attribute:" + provisionParentAttrs[a]);
            }
            
            provisionChildAttrs = getProcessFormChildValues(instanceKey, "UD_GRES_PC", false, null);
            mylogger.debug("Show Child Attributes");
            for(int a=0;a<provisionChildAttrs.length;a++)
            {
                mylogger.debug("Child Attribute:" + provisionChildAttrs[a]);
            }
            
            childTableName = (String)provisionParentData.get("UD_GRES_P_ROLE_TABLENAME");
            
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE;
        }
        
        try
        {
            long objKey = this.getResourceObject(resName);
            long usrKey = this.getOimUser(targetUser);
            mylogger.debug("Provisioning Resource " + resName + " To " + targetUser);
            
            
            String resourceType = this.getProcessDefinitionType(resName);
            // approval types require object forms
            if (resourceType.equalsIgnoreCase("Approval"))
            {
                Map objectForm = new HashMap();
                for(int pa=0;pa<provisionParentAttrs.length;pa++)
                {
                    Map attr = provisionParentAttrs[pa];
                    mylogger.debug("Parent Attribute:" + attr);
                    objectForm.put(attr.get("UD_GRES_PA_NAME"),attr.get("UD_GRES_PA_VALUE"));
                }
                mylogger.debug("Got Object Form:" + objectForm);
                
                // now handle the child records
                if (childTableName != null && childTableName.trim().length() > 0)
                {
                    mylogger.debug("Processing Resource Object Child Entries:" + childTableName);
                    long lKey = objFormUtils.getObjectFormDefinitionKey(childTableName);
                    String defKey = Long.toString(lKey);
                    List<Map> childRecords = buildTargetResourceChildRecords(provisionChildAttrs);
                    Map[] childMaps = (Map[]) childRecords.toArray(new HashMap[0]);
                    Map parmMap = new HashMap();
                    parmMap.put(defKey,childMaps);
                    long obi = provOps.createObjectInstanceWithObjectAndChildFormData(usrKey, objKey, objectForm, parmMap);
                    if (obi == 0)
                    {
                        mylogger.error("Invalid OBI Provisioned");
                        throw new Exception("Invalid OBI Provisioned:" + resName + ":" + targetUser);
                    }
                }
                else
                {
                    long obi = provOps.createObjectInstanceWithObjectFormData(usrKey, objKey, objectForm);
                    if (obi == 0)
                    {
                        mylogger.error("Invalid OBI Provisioned");
                        throw new Exception("Invalid OBI Provisioned:" + resName + ":" + targetUser);
                    }
                }
            }
            else
            {
                ResourceData rd = this.provisionNewResource(targetUser, objKey);
                mylogger.debug("RD:" + rd.getObiKey() + ":" + rd.getOioKey() + ":" + rd.getOiuKey());
                long newProcessInstanceKey = this.getProcessInstanceKey(Long.parseLong(rd.getObiKey()), usrKey);
                mylogger.debug("PIK FROM OBI:" + newProcessInstanceKey);
                
                Map procForm = new HashMap();
                for(int pa=0;pa<provisionParentAttrs.length;pa++)
                {
                    Map attr = provisionParentAttrs[pa];
                    mylogger.debug("Parent Attribute:" + attr);
                    procForm.put(attr.get("UD_GRES_PA_NAME"),attr.get("UD_GRES_PA_VALUE"));
                }
                mylogger.debug("Got Process Form:" + procForm);
                this.setProcessFormValues(newProcessInstanceKey, procForm);
                procForm = this.getProcessFormValues(newProcessInstanceKey, true, null);
                // now handle the child records
                if (childTableName != null && childTableName.trim().length() > 0)
                {
                    mylogger.debug("Processing Resource Child Entries");
                    List<Map> childRecords = buildTargetResourceChildRecords(provisionChildAttrs);
                    for(Map m : childRecords)
                    {
                        this.addProcessFormChildValue(newProcessInstanceKey, childTableName, m);
                    }
                }
            }
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            return FAILURE;
        }
        return SUCCESS;
    }

    private List<Map> buildTargetResourceChildRecords(Map[] childRecords)
    {
        List<Map> recordList = new ArrayList<Map>();
        Map<String,Map> sequencedMap = new HashMap<String,Map>();
        Map recordMap = null;
        for(int i=0;i<childRecords.length;i++)
        {
            Map mattr = childRecords[i];
            String seq = (String)mattr.get("UD_GRES_PC_SEQNUM");
            recordMap = sequencedMap.get(seq);
            if (recordMap == null)
                recordMap = new HashMap();
            recordMap.put(mattr.get("UD_GRES_PC_NAME"),mattr.get("UD_GRES_PC_VALUE"));
            sequencedMap.put(seq,recordMap);
            
        }
        Set<String> keySet = sequencedMap.keySet();
        for(String k : keySet)
        {
            recordList.add(sequencedMap.get(k));
        }
        
        return recordList;
    }
    
    private String getProcessDefinitionType(String resourceName) throws Exception
    {
        String tableName = null;
        String resType = null;
        try
        {
            Map[] resourceInfo = this.getResourceFormInfo(resourceName);
            for(int m=0;m<resourceInfo.length;m++)
            {
                Map ri = resourceInfo[m];
                String type = (String)ri.get("Process Definition.Type");
                if (type.equalsIgnoreCase("Provisioning"))
                    resType = type;
                if (type.equalsIgnoreCase("Approval"))
                    resType = type;
            }
        }
        catch(Exception e)
        {
            mylogger.error("APIError",e);
            throw e;
        }
        return resType;
    }
    
    private Map findMatchingChildRecord(Map[] currentChildren, Map child)
    {
        if (currentChildren == null || currentChildren.length == 0)
            return null;
        if (child == null || child.isEmpty())
            return null;
        
        Set<String> keys = child.keySet();
        
        for(Map curr : currentChildren)
        {
            boolean matched = true;
            for(String k : keys)
            {
                String currv = (String)curr.get(k);
                String cval = (String)child.get(k);
                if (currv == null || cval == null)
                {
                    matched = false;
                    continue;
                }
                if (!currv.equals(cval))
                {
                    matched = false;
                }
            }
            if (matched)
            {
                return curr;
            }
        }
        return null;
    }
    
    private void checkStatus()
    {
        /*
        for(int i=0;i<userObjects.length;i++)
        {
            Map obj = userObjects[i];
            mylogger.debug("Object Map:" + obj);
            String objStatus = (String)obj.get("Status");
            objStatus = (objStatus == null ? "" : objStatus);
            if ("Enabled".equalsIgnoreCase(objStatus) || "Provisioned".equalsIgnoreCase(objStatus))
            {
                mylogger.error("User Already Has Resource:" + resName);
                return FAILURE + "User Already Has Resource:" + resName;
            }
        }

        for(int i=0;i<userObjects.length;i++)
        {
            Map obj = userObjects[i];
            mylogger.debug("Object Map:" + obj);
            String objStatus = (String)obj.get("Status");
            objStatus = (objStatus == null ? "" : objStatus);
            if ("Disabled".equalsIgnoreCase(objStatus))
            {
                mylogger.debug("Enabling Resource:" + resName + " for user " + targetUser);
                String obiKeyStr = (String)obj.get("ObjectInstanceKey");
                long obiKey = Long.parseLong(obiKeyStr);
                //this.enableOimResource(targetUser, obiKey);
                //return FAILURE + "User Already Has Resource:" + resName;
            }
        }
        */
    }
}